home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / ISSUE14 / TLIST / LISTDEMO.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1996-07-25  |  16.4 KB  |  536 lines

  1. {This program demonstrates the use of lists to hold a database table.
  2.   The lists index the table by three methods. For extra speed the indexes
  3.   have binary searches that are FAR more complicated than simple loops
  4.   but which work exponentially faster}
  5.  
  6. { Program by J.Morgan 102247.2027@compuserve.com
  7.  
  8.   It is freeware and that means don't blame me if it falls over!
  9.   It is also just a demonstration and not supposed to be bullet-proof, or
  10.   even particularly "tightly" coded}
  11.  
  12. { TO RUN: Needs an Alias entered in the BDE "CategoriesDB"
  13.        Nothing special about it -- it should just be of type
  14.        Interbase and point to the ctgrs.gdb file }
  15.  
  16. unit ListDemo;
  17.  
  18. interface
  19.  
  20. uses
  21.   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  22.   StdCtrls, Mask, Grids, Outline, DB, DBTables;
  23.  
  24. type
  25.     {this class matches the fields in the Database table}
  26.   TCategory = class
  27.     CategoryID,
  28.     OutLinePosition,
  29.     OutLineParent : Longint;
  30.     Description : string[40];
  31.   end;
  32.  
  33.   TCatIndex = (ciDescription, ciOutlinePosition, ciCategoryID);
  34.  
  35.     {This class holds three indexes to a list of records. It also gives
  36.      a few extra functions to make life a little easier. AddRecord
  37.      inserts a new record into the lists in the correct position.
  38.      Search xxx finds the record with the entered variable. First and
  39.      next make looping through the lists easy (and possible)}
  40.   TCategoryContainer = class
  41.   private
  42.     ByDescription,
  43.     ByOutlinePosition,
  44.     ByCategoryID : TList;
  45.  
  46.     fCatIndex : TCatIndex;
  47.     fCurrentIndex : LongInt;
  48.   public
  49.     procedure AddRecord( ACategory : TCategory );
  50.     function SearchDescription( s : string) : TCategory;
  51.     function SearchOutlinePosition( i : LongInt) : TCategory;
  52.     function SearchCategoryID( i : LongInt) : TCategory;
  53.     function First( CatIndex : TCatIndex ) : TCategory;
  54.     function Next : TCategory;
  55.  
  56.     constructor Create;
  57.     destructor Destroy; override;
  58.   end;
  59.  
  60.   TfrmCategories = class(TForm)
  61.     Outline1: TOutline;
  62.     Label1: TLabel;
  63.     Button1: TButton;
  64.     Button2: TButton;
  65.     Button3: TButton;
  66.     Button4: TButton;
  67.     ListBox1: TListBox;
  68.     Label2: TLabel;
  69.     Label3: TLabel;
  70.     lblTime: TLabel;
  71.     Label4: TLabel;
  72.     Label5: TLabel;
  73.     Label6: TLabel;
  74.     edtDescription: TEdit;
  75.     edtIndex: TMaskEdit;
  76.     edtID: TMaskEdit;
  77.     Button5: TButton;
  78.     Button6: TButton;
  79.     Button7: TButton;
  80.     Label7: TLabel;
  81.     Edit1: TEdit;
  82.     Query1: TQuery;
  83.     Database1: TDatabase;
  84.     procedure Button1Click(Sender: TObject);
  85.     procedure Button3Click(Sender: TObject);
  86.     procedure FormCreate(Sender: TObject);
  87.     procedure FormDestroy(Sender: TObject);
  88.     procedure Button4Click(Sender: TObject);
  89.     procedure Button2Click(Sender: TObject);
  90.     procedure Button5Click(Sender: TObject);
  91.     procedure Button6Click(Sender: TObject);
  92.     procedure Button7Click(Sender: TObject);
  93.   private
  94.     { Private declarations }
  95.     CatContainer : TCategoryContainer;
  96.     StartTime : TDateTime;
  97.  
  98.     procedure StartOp;
  99.     procedure EndOp;
  100.     procedure ShowResult( ACategory : TCategory );
  101.   public
  102.     { Public declarations }
  103.   end;
  104.  
  105. var
  106.   frmCategories: TfrmCategories;
  107.  
  108. implementation
  109.  
  110. {$R *.DFM}
  111.  
  112. constructor TCategoryContainer.Create;
  113. begin
  114.   inherited Create;
  115.  
  116.   ByDescription:=TList.Create;
  117.   ByOutLinePosition:=TList.Create;
  118.   ByCategoryID:=TList.Create;
  119. end;
  120.  
  121. destructor TCategoryContainer.Destroy;
  122. var
  123.   i: LongInt;
  124. begin
  125.   for i:=0 to ByCategoryID.Count-1 do
  126.     TCategory(ByDescription[i]).Free;
  127.  
  128.   ByDescription.Free;
  129.   ByOutLinePosition.Free;
  130.   ByCategoryID.Free;
  131.  
  132.   inherited Destroy;
  133. end;
  134.  
  135. procedure TCategoryContainer.AddRecord( ACategory : TCategory );
  136. var
  137.   i, delta, CurrInt : LongInt;
  138.   CurrStr : string;
  139.  
  140.          {The most complicated things in this program are the binary search
  141.          functions. This one recursively iterates through the list-so-far,
  142.          looking for where the new record should be inserted. Once it finds
  143.          a position where the description is <= to the new record's description,
  144.          and the following positon has a description >= then it returns that
  145.          position's index in the list. We could have simply looped from start
  146.          to finish looking for the right position to insert but this method will
  147.          be far quicker on big lists.
  148.          Because we have three indexes we will create one of these functions for
  149.          each index type}
  150.   function DescAppendPos( index : LongInt ) : LongInt;
  151.   var
  152.     Prev, Next : string;
  153.     isPrev, isNext : boolean;
  154.   begin
  155.          {Get the description at and after the position we are looking at}
  156.     isPrev:=index>=0;
  157.     isNext:=index+1<ByDescription.Count;
  158.     if isPrev then Prev:=TCategory(ByDescription[index]).Description;
  159.     if isNext then Next:=TCategory(ByDescription[index+1]).Description;
  160.  
  161.          {Set result is CurrStr is between Prev and Next}
  162.     if ((isPrev and (CurrStr>=Prev) and (not isNext or (CurrStr<=Next)))) or
  163.        ((not isPrev and (not isNext or (CurrStr<=Next)))) then Result:=index
  164.     else {we are not at the right position so move a half distance}
  165.     begin
  166.       delta:=delta div 2; {create a half distance}
  167.       if delta=0 then delta:=1;
  168.  
  169.       if Prev<=CurrStr then Result:=DescAppendPos( index + Delta )
  170.       else               Result:=DescAppendPos( index - Delta )
  171.     end;
  172.   end;
  173.  
  174.   function IDAppendPos( index : LongInt ) : LongInt; {See DescAppendPos comments}
  175.   var
  176.     Prev, Next : LongInt;
  177.     isPrev, isNext : boolean;
  178.   begin
  179.     isPrev:=index>=0;
  180.     isNext:=index+1<ByCategoryID.Count;
  181.     if isPrev then Prev:=TCategory(ByCategoryID[index]).CategoryID;
  182.     if isNext then Next:=TCategory(ByCategoryID[index+1]).CategoryID;
  183.     if ((isPrev and (CurrInt>=Prev) and (not isNext or (CurrInt<=Next)))) or
  184.        ((not isPrev and (not isNext or (CurrInt<=Next)))) then Result:=index
  185.     else
  186.     begin
  187.       delta:=delta div 2;
  188.       if delta=0 then delta:=1;
  189.       if Prev<=CurrInt then Result:=IDAppendPos( index + Delta )
  190.       else               Result:=IDAppendPos( index - Delta )
  191.     end;
  192.   end;
  193.  
  194.   function PositionAppendPos( index : LongInt ) : LongInt;  {See DescAppendPos comments}
  195.   var
  196.     Prev, Next : LongInt;
  197.     isPrev, isNext : boolean;
  198.   begin
  199.     isPrev:=index>=0;
  200.     isNext:=index+1<ByOutlinePosition.Count;
  201.     if isPrev then Prev:=TCategory(ByOutlinePosition[index]).OutlinePosition;
  202.     if isNext then Next:=TCategory(ByOutlinePosition[index+1]).OutlinePosition;
  203.     if ((isPrev and (CurrInt>=Prev) and (not isNext or (CurrInt<=Next)))) or
  204.        ((not isPrev and (not isNext or (CurrInt<=Next)))) then Result:=index
  205.     else
  206.     begin
  207.       delta:=delta div 2;
  208.       if delta=0 then delta:=1;
  209.       if Prev<=CurrInt then Result:=PositionAppendPos( index + Delta )
  210.       else               Result:=PositionAppendPos( index - Delta )
  211.     end;
  212.   end;
  213.  
  214. begin {Add Record}
  215.   {------------------------------------------------------}
  216.       {need to add the record into the Description list...}
  217.   if ByDescription.Count=0 then i:=0
  218.   else begin
  219.       {First we need the position on the list where to insert the record}
  220.     delta:=(ByDescription.Count div 2);
  221.     CurrStr:=ACategory.Description;
  222.       {Then we will call a binary search to find where to put this record}
  223.     i:=DescAppendPos(delta)+1;
  224.   end;
  225.       {And finally we insert the record in the description list}
  226.   ByDescription.Insert(i, ACategory);
  227.   {------------------------------------------------------}
  228.       {now the same for the CategoryID list...}
  229.   if ByCategoryID.Count=0 then i:=0
  230.   else begin
  231.     delta:=(ByCategoryID.Count div 2);
  232.     CurrInt:=ACategory.CategoryID;
  233.     i:=IDAppendPos(delta)+1;
  234.   end;
  235.   ByCategoryID.Insert(i, ACategory);
  236.   {------------------------------------------------------}
  237.       {and the same for the OutlinePosition list...}
  238.   if ByOutLinePosition.Count=0 then i:=0
  239.   else begin
  240.     delta:=(ByOutLinePosition.Count div 2);
  241.     CurrInt:=ACategory.OutlinePosition;
  242.     i:=PositionAppendPos(delta)+1;
  243.   end;
  244.   ByOutlinePosition.Insert(i, ACategory);
  245. end;
  246.  
  247. function TCategoryContainer.SearchDescription( s : string) : TCategory;
  248. var
  249.   delta : LongInt;
  250.           {Binary search the list for the "s" string}
  251.   function DescPos( index : LongInt ) : TCategory;
  252.   var
  253.     ACategory: TCategory;
  254.   begin
  255.     if (index<0) or (index>=ByDescription.Count) then Result:=nil
  256.     else begin
  257.          {Get the description at and after the position we are looking at}
  258.       ACategory:=TCategory(ByDescription[index]);
  259.       if ACategory.Description=s then Result:=ACategory
  260.       else begin
  261.         delta:=delta div 2; {create a half distance}
  262.         if delta=0 then delta:=1;
  263.           {decide whether to carry on looking and if so in which direction}
  264.         if (ACategory.Description<s) and (index+1<ByDescription.Count) and
  265.            (TCategory(ByDescription[index+1]).Description<=s)
  266.                then Result:=DescPos( index + Delta )
  267.         else if (ACategory.Description>s) and (index-1>=0) and
  268.            (TCategory(ByDescription[index-1]).Description>=s)
  269.                then Result:=DescPos( index - Delta )
  270.         else Result:=nil
  271.       end;
  272.     end;
  273.   end;
  274.  
  275. begin {Find Record}
  276.   if ByDescription.Count=0 then Result:=nil
  277.   else begin
  278.       {Start from the middle of the list}
  279.     delta:=(ByDescription.Count div 2);
  280.       {Then we will call a binary search to find where the description is}
  281.     Result:=DescPos(delta);
  282.   end;
  283. end;
  284.  
  285. function TCategoryContainer.SearchOutlinePosition( i : LongInt) : TCategory;
  286. var
  287.   delta : LongInt;
  288.           {Binary search the list for the "i" Position}
  289.   function PositionPos( index : LongInt ) : TCategory;
  290.   var
  291.     ACategory: TCategory;
  292.   begin
  293.     if (index<0) or (index>=ByOutlinePosition.Count) then Result:=nil
  294.     else begin
  295.          {Get the OutlinePosition at and after the position we are looking at}
  296.       ACategory:=TCategory(ByOutlinePosition[index]);
  297.       if ACategory.OutlinePosition=i then Result:=ACategory
  298.       else begin
  299.         delta:=delta div 2; {create a half distance}
  300.         if delta=0 then delta:=1;
  301.           {decide whether to carry on looking and if so in which direction}
  302.         if (ACategory.OutlinePosition<i) and (index+1<ByOutlinePosition.Count) and
  303.            (TCategory(ByOutlinePosition[index+1]).OutlinePosition<=i)
  304.                then Result:=PositionPos( index + Delta )
  305.         else if (ACategory.OutlinePosition>i) and (index-1>=0) and
  306.            (TCategory(ByOutlinePosition[index-1]).OutlinePosition>=i)
  307.                then Result:=PositionPos( index - Delta )
  308.         else Result:=nil
  309.       end;
  310.     end;
  311.   end;
  312.  
  313. begin {Find Record (by Position)}
  314.   if ByOutlinePosition.Count=0 then Result:=nil
  315.   else begin
  316.       {Start from the middle of the list}
  317.     delta:=(ByOutlinePosition.Count div 2);
  318.       {Then we will call a binary search to find where the OutlinePosition is}
  319.     Result:=PositionPos(delta);
  320.   end;
  321. end;
  322.  
  323. function TCategoryContainer.SearchCategoryID( i : LongInt) : TCategory;
  324. var
  325.   delta : LongInt;
  326.           {Binary search the list for the "i" Category}
  327.   function CategoryPos( index : LongInt ) : TCategory;
  328.   var
  329.     ACategory: TCategory;
  330.   begin
  331.     if (index<0) or (index>=ByCategoryID.Count) then Result:=nil
  332.     else begin
  333.          {Get the CategoryID at and after the Category we are looking at}
  334.       ACategory:=TCategory(ByCategoryID[index]);
  335.       if ACategory.CategoryID=i then Result:=ACategory
  336.       else begin
  337.         delta:=delta div 2; {create a half distance}
  338.         if delta=0 then delta:=1;
  339.           {decide whether to carry on looking and if so in which direction}
  340.         if (ACategory.CategoryID<i) and (index+1<ByCategoryID.Count) and
  341.            (TCategory(ByCategoryID[index+1]).CategoryID<=i)
  342.                then Result:=CategoryPos( index + Delta )
  343.         else if (ACategory.CategoryID>i) and (index-1>=0) and
  344.            (TCategory(ByCategoryID[index-1]).CategoryID>=i)
  345.                then Result:=CategoryPos( index - Delta )
  346.         else Result:=nil
  347.       end;
  348.     end;
  349.   end;
  350.  
  351. begin {Find Record (by Category)}
  352.   if ByCategoryID.Count=0 then Result:=nil
  353.   else begin
  354.       {Start from the middle of the list}
  355.     delta:=(ByCategoryID.Count div 2);
  356.       {Then we will call a binary search to find where the CategoryID is}
  357.     Result:=CategoryPos(delta);
  358.   end;
  359. end;
  360.  
  361. function TCategoryContainer.First( CatIndex : TCatIndex ) : TCategory;
  362. begin
  363.   fCatIndex:=CatIndex;
  364.   fCurrentIndex:=-1;
  365.   Result:=Next;
  366. end;
  367.  
  368. function TCategoryContainer.Next : TCategory;
  369. begin
  370.   Inc(fCurrentIndex);
  371.   case fCatIndex of
  372.     ciDescription :
  373.       if fCurrentIndex>=ByDescription.Count then Result:=nil
  374.       else Result:=TCategory(ByDescription[fCurrentIndex]);
  375.     ciOutlinePosition :
  376.       if fCurrentIndex>=ByOutlinePosition.Count then Result:=nil
  377.       else Result:=TCategory(ByOutlinePosition[fCurrentIndex]);
  378.     ciCategoryID :
  379.       if fCurrentIndex>=ByCategoryID.Count then Result:=nil
  380.       else Result:=TCategory(ByCategoryID[fCurrentIndex]);
  381.   end;
  382. end;
  383.  
  384. {--------------------------TfrmCategories----------------------------}
  385. procedure TfrmCategories.Button1Click(Sender: TObject);
  386. begin
  387.   OutLine1.Clear;
  388.   StartOp;
  389.   with Query1 do
  390.   begin
  391.     sql.clear;
  392.     sql.add('SELECT DESCRIPTION, OUTLINEPARENT FROM CATEGORIES');
  393.     sql.add('ORDER BY OUTLINEPOSITION');
  394.     Open;
  395.     while not eof do
  396.     begin
  397.       Outline1.AddChild(Fields[1].AsInteger, Fields[0].AsString);
  398.       Next;
  399.     end;
  400.     Close;
  401.   end;
  402.   EndOp;
  403. end;
  404.  
  405. procedure TfrmCategories.Button3Click(Sender: TObject);
  406. begin
  407.   ListBox1.Clear;
  408.   StartOp;
  409.   with Query1 do
  410.   begin
  411.     sql.clear;
  412.     sql.add('SELECT DESCRIPTION FROM CATEGORIES');
  413.     sql.add('ORDER BY DESCRIPTION');
  414.     Open;
  415.     while not eof do
  416.     begin
  417.       ListBox1.items.Add(Fields[0].AsString);
  418.       Next;
  419.     end;
  420.     Close;
  421.   end;
  422.   EndOp;
  423. end;
  424.  
  425. procedure TfrmCategories.FormCreate(Sender: TObject);
  426. var
  427.   ACategory : TCategory;
  428. begin
  429.      {create the container}
  430.   CatContainer:=TCategoryContainer.Create;
  431.  
  432.      {then fill it up with the records currently in the table}
  433.   with Query1 do
  434.   begin
  435.     sql.clear;
  436.     sql.add('SELECT CATEGORYID, OUTLINEPOSITION, OUTLINEPARENT, DESCRIPTION FROM CATEGORIES');
  437.     Open;
  438.     while not eof do
  439.     begin
  440.       ACategory:=TCategory.Create;
  441.       with ACategory do
  442.       begin
  443.         CategoryID:=Fields[0].AsInteger;
  444.         OutlinePosition:=Fields[1].AsInteger;
  445.         OutlineParent:=Fields[2].AsInteger;
  446.         Description:=Fields[3].AsString;
  447.       end;
  448.       CatContainer.AddRecord(ACategory);
  449.       Next;
  450.     end;
  451.  
  452.     Close;
  453.   end;
  454. end;
  455.  
  456. procedure TfrmCategories.FormDestroy(Sender: TObject);
  457. begin
  458.   CatContainer.Free;
  459. end;
  460.  
  461. procedure TfrmCategories.Button4Click(Sender: TObject);
  462. var
  463.   ACategory : TCategory;
  464. begin
  465.   ListBox1.Clear;
  466.   StartOp;
  467.   with CatContainer do
  468.   begin
  469.     ACategory:=First(ciDescription);
  470.     while ACategory<>nil do
  471.     begin
  472.       ListBox1.Items.Add(ACategory.Description);
  473.       ACategory:=Next;
  474.     end;
  475.   end;
  476.   EndOp;
  477. end;
  478.  
  479. procedure TfrmCategories.Button2Click(Sender: TObject);
  480. var
  481.   ACategory : TCategory;
  482.  
  483. begin
  484.   OutLine1.Clear;
  485.   StartOp;
  486.   with CatContainer do
  487.   begin
  488.     ACategory:=First(ciOutlinePosition);
  489.     while ACategory<>nil do
  490.     begin
  491.       Outline1.AddChild(ACategory.OutlineParent, ACategory.Description);
  492.       ACategory:=Next;
  493.     end;
  494.   end;
  495.   EndOp;
  496. end;
  497.  
  498. procedure TfrmCategories.StartOp;
  499. begin
  500.   StartTime:=time;
  501. end;
  502.  
  503. procedure TfrmCategories.EndOp;
  504. var
  505.   Hour, Min, Sec, MSec : Word;
  506.  
  507. begin
  508.   DecodeTime(time-startTime, Hour, Min, Sec, MSec);
  509.   lblTime.caption:=format('%2.2d:%2.2d.%d', [Min, Sec, MSec]);
  510. end;
  511.  
  512. procedure TfrmCategories.Button5Click(Sender: TObject);
  513. begin
  514.   ShowResult( CatContainer.SearchDescription(edtDescription.Text) );
  515. end;
  516.  
  517. procedure TfrmCategories.ShowResult(ACategory : TCategory);
  518. begin
  519.   if ACategory=nil then Edit1.Text:='Category Not Found'
  520.   else Edit1.Text:=ACategory.Description+', '+IntToStr(Acategory.CategoryID);
  521. end;
  522.  
  523. procedure TfrmCategories.Button6Click(Sender: TObject);
  524. begin
  525.   if edtIndex.text<>'' then
  526.     ShowResult( CatContainer.SearchOutlinePosition(StrToInt(edtIndex.Text)) );
  527. end;
  528.  
  529. procedure TfrmCategories.Button7Click(Sender: TObject);
  530. begin
  531.   if edtID.text<>'' then
  532.     ShowResult( CatContainer.SearchCategoryID(StrToInt(edtID.Text)) );
  533. end;
  534.  
  535. end.
  536.